package dkbta.server.domain.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Hashtable;
import java.util.concurrent.LinkedBlockingQueue;

import org.apache.log4j.Logger;

import dkbta.Config;
import dkbta.server.gui.Observable;
import dkbta.server.gui.Observer;
import dkbta.server.util.exceptions.FailedToAcceptClientException;
import dkbta.server.util.exceptions.FailedToReadQueryException;
import dkbta.server.util.query.Query;
import dkbta.server.util.query.Results;



/**
 * @author Arkady Mishiev
 *
 */

public class ClientManager extends Thread implements Observable{

	/* 
	 * Results that accepted from Units through Stub and Coordinator
	 * been used by InboxHandler that dispatch each Result to its destination (ClientStub)
	 */
	private LinkedBlockingQueue<Results> m_globalResultsInbox;
	/* 
	 * Queries that accepted by all ClientStubs. To be moved to CUStubs through Coordinator
	 * Used by ClientStubs 
	 */
	private LinkedBlockingQueue<Query> m_globalQueryOutbox;
	private static Logger s_logger;
	private boolean m_shouldRun;
	private ServerSocket m_serverSocket = null;
	private int m_serverPort;
	private Observer m_logsPanelObserver;
	private Hashtable<String, ClientStub> m_clients;
	private int m_clientCount;
	private InboxDispatcher m_inboxDispatcher;
        private Observer m_plansPanelObserver;

	public ClientManager(LinkedBlockingQueue<Query> outbox,
			LinkedBlockingQueue<Results> inbox) {
		super("ClientMgr");
		m_globalResultsInbox = inbox;
		m_globalQueryOutbox = outbox;
		s_logger = Logger.getLogger(ClientManager.class);
		m_serverPort = Config.getInt("CLIENTS_REGISTRY_PORT", 5000);
		m_shouldRun = true;
		m_clientCount = 0;
		m_inboxDispatcher = new InboxDispatcher();
		m_clients = new Hashtable<String, ClientStub>();
	}

	public void run(){
		m_inboxDispatcher.start();
		openServerSocket();						
		while(m_shouldRun){			
			Socket clientSocket;
			try {
				s_logger.info("Waiting for connection request from client");
				m_logsPanelObserver.updateLogsPanel("Waiting for connection request from client");
				clientSocket = m_serverSocket.accept();
				String id = (++m_clientCount) + "@" + 
				clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort();
				ClientStub client = new ClientStub(clientSocket, id, m_globalQueryOutbox);
				s_logger.info("Client connected. id " + id);
				m_logsPanelObserver.updateLogsPanel("Client connected. id " + id);				
				client.addLogsPanelObserver(m_logsPanelObserver);
                                client.addQueriesPanelObserver(m_plansPanelObserver);
				client.start();
				synchronized (m_clients) {
					m_clients.put(id, client);	
				}							
			} catch (IOException e) {
				// TODO add switch that checks if it was fatal error or not
				// if yes then System.exit
				s_logger.warn("Failed while waiting for clients connection");
				m_logsPanelObserver.updateLogsPanel("Failed while waiting for clients connection");				
			}

		}
	}

	private void openServerSocket() {
		try {
			m_serverSocket = new ServerSocket(m_serverPort);
			s_logger.info("Ready on port :" + m_serverPort);
			m_logsPanelObserver.updateLogsPanel("[ClientMgr] Ready on port :" 
					+ m_serverPort);
		} catch (IOException e) {
			s_logger.fatal("Could not listen on port : " + m_serverPort + 
			"\nClosing ...");
			m_logsPanelObserver.updateLogsPanel("[ClientMgr] could not listen on port "
					+ m_serverPort + "\nClosing ...");
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e1) {}
			System.exit(-1); 
		}
	}

	@Override
	public void addCUPanelObserver(Observer o) {
		// TODO Auto-generated method stub

	}

	@Override
	public void addLogsPanelObserver(Observer o) {
		m_logsPanelObserver = o;		
	}

	@Override
	public void addQueriesPanelObserver(Observer o) {
		m_plansPanelObserver = o;
	}

	private class InboxDispatcher extends Thread{		
		
		public void run(){
			while(m_shouldRun){
				try {
					s_logger.info("Waiting for Results");
					m_logsPanelObserver.updateLogsPanel("Waiting for Results"); 
					Results results = m_globalResultsInbox.take();
					String clientId = results.getClientId();
					s_logger.info("Results received for client " + clientId);
					m_logsPanelObserver.updateLogsPanel("Results received for client " + clientId); 
					/*synchronize with clientRegistry*/
					synchronized (m_clients) {
						ClientStub client = m_clients.get(clientId);
						/*if the client is not closed*/
						if(client != null){
							client.acceptResults(results);
							s_logger.warn("Results dispatched to clientStub " + clientId);
							m_logsPanelObserver.updateLogsPanel("Results dispatched to clientStub " + clientId);
						}
						else{
							s_logger.warn("Cant dispatch results for client " + clientId);
							m_logsPanelObserver.updateLogsPanel("Cant dispatch results for client " + clientId);
						}
					}
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					s_logger.warn("Interrupted while waiting for results");
					m_logsPanelObserver.updateLogsPanel("Interrupted while waiting for results");
				}
			}
		}
	}
}
